
package stratego.java.ssm;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.InputEvent;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import stratego.java.ssm.gui.BoardEvent;
import stratego.java.ssm.gui.BoardEventListener;
import stratego.java.ssm.gui.GameCanvas;
import stratego.java.ssm.gui.PieceSelector;
import stratego.java.ssm.online.Client;
import stratego.java.ssm.online.Messages;
import stratego.java.ssm.online.Server;
import stratego.java.ssm.piece.EmptyPiece;
import stratego.java.ssm.piece.Piece;
import stratego.java.ssm.piece.PieceUtilities;

/**
 * <code>public class OnlineGame</code>
 * <p>
 * The class that handles online gameplay. It's responsibilities are to make sure the playing
 * experience is similar to an offline game session (minus the flipping visibility of the player's
 * pieces).
 * </p>
 * 
 * @author Seth Aaron
 * @author Mark Aligbe
 * 
 */
public class OnlineGame implements BoardEventListener
{
	private JFrame	                                    frame;
	private JDialog	                                    waitingMove;
	private Object	                                    lock;
	private Scanner	                                    netIn;
	private PrintWriter	                                netOut;
	private Socket	                                    conn;
	private Server	                                    server;
	public Board	                                    board;
	private int	                                        port;
	private PieceSelector	                            selector;
	private GameCanvas	                                canvas;
	private Player[]	                                players;
	private LinkedBlockingQueue<HashMap<Point, String>>	tempPieces;
	private final int	                                totalPieces	= 40;
	private Player	                                    currentPlayer;
	private Player	                                    winner;
	private AtomicBoolean	                            meFirst;
	
	private Messages	                                stream;
	// A queue holding the currently clicked pieces, for deciding if something needs to be done
	private Piece[]	                                    pieceQueue;
	// A queue for holding the points of the currently clicked pieces.
	private Point[]	                                    pointQueue;
	private Object	                                    moveLock;
	private AtomicBoolean	                            notified;
	
	/**
	 * <code>public OnlineGame(JFrame frame, GameCanvas canvas)</code>
	 * <p>
	 * Begins an online session of Stratego. Determines if this machine should be the host or
	 * client, and interacts with the destination machine to play online Stratego.
	 * </p>
	 * 
	 * @param frame
	 *            - The main frame of the Stratego game. Contains the GameCanvas object
	 * @param canvas
	 *            - The GameCanvas of the Stratego game.
	 * @param input
	 */
	public OnlineGame(JFrame frame, GameCanvas canvas, JTextArea input)
	{
		// First, we want to set up the Dialog to take in the user's game mode choice
		this.frame = frame;
		this.canvas = canvas;
		canvas.registerBoardListener(this);
		tempPieces = new LinkedBlockingQueue<HashMap<Point, String>>();
		meFirst = new AtomicBoolean(false);
		notified = new AtomicBoolean(false);
		lock = new Object();
		moveLock = new Object();
		// Assume Normal Board, because we're cool
		board = new Board(Board.CLASSIC, 1337);
		// Keep prompting the user for a good server until we get one.
		boolean connected = false;
		while (!connected)
		{
			Object[] modeInfo = getGameMode(frame);
			// The user indicated they want to quit.
			if ((Integer) modeInfo[0] == -1)
				System.exit(0);
			// Regardless of whether or not we're the server or client, all we want are the input
			// and output streams
			if ((Integer) modeInfo[0] == 0)
			{
				Client c = new Client((String) modeInfo[1], (Integer) modeInfo[2]);
				if (c.connected())
				{
					netIn = new Scanner(c.getSocketInputStream());
					netOut = new PrintWriter(c.getSocketOutputStream(), true);
					conn = c.getSocket();
					connected = true;
					// Initiate the Message streamer to enable the sending and receiving of
					// messages.
					try
					{
						stream = new Messages(input, netIn, netOut, this, conn.getSoTimeout());
						stream.inSetup.set(true);
						(new Thread(stream)).start();
					}
					catch (SocketException e)
					{
						e.printStackTrace();
					}
					catch (IOException e)
					{
						e.printStackTrace();
					}
					JOptionPane.showMessageDialog(frame, "Connected to: "
					        + conn.getInetAddress().toString().substring(1) + ":" + conn.getPort(),
					        "Connection Successful", JOptionPane.INFORMATION_MESSAGE);
				}
				else
				{
					JOptionPane.showMessageDialog(frame, "Failed to connect to: " + modeInfo[1]
					        + ":" + modeInfo[2], "Connection Failure", JOptionPane.ERROR_MESSAGE);
				}
			}
			else if ((Integer) modeInfo[0] == 1)
			{
				port = (Integer) modeInfo[2];
				waitForConnection();
				// If server is null, then a connection was never made
				if (server == null)
					continue;
				if (server.connected())
				{
					netIn = new Scanner(server.getSocketInputStream());
					netOut = new PrintWriter(server.getSocketOutputStream(), true);
					conn = server.getSocket();
					connected = true;
					// Initiate the Message streamer to enable the sending and receiving of
					// messages.
					try
					{
						stream = new Messages(input, netIn, netOut, this, conn.getSoTimeout());
						stream.inSetup.set(true);
						(new Thread(stream)).start();
					}
					catch (SocketException e)
					{
						e.printStackTrace();
					}
					catch (IOException e)
					{
						e.printStackTrace();
					}
					JOptionPane.showMessageDialog(frame, "Connected to: "
					        + conn.getInetAddress().toString().substring(1) + ":" + conn.getPort(),
					        "Connection Successful", JOptionPane.INFORMATION_MESSAGE);
				}
			}
		}
		setupGame();
		playGame();
	}
	
	/**
	 * <code>private void waitForConnection()</code>
	 * <p>
	 * Initiates threads to: 1) wait for a client connection, 2) display the waiting status to the
	 * user, and 3) animate the waiting status. It also allows the user to cancel waiting and return
	 * to the mode selection.
	 * </p>
	 */
	private void waitForConnection()
	{
		// server = new Server((Integer) modeInfo[2]);
		// Make a new waiting indicator. Creates a JOptionPane that shows to the user the
		// status of the ServerSocket
		JLabel wait = new JLabel("Waiting for a connection");
		// End waiting indicator.
		JPanel waitingCenter = new JPanel(new FlowLayout(FlowLayout.CENTER));
		waitingCenter.add(wait);
		Thread waitingThread = new Thread(new WaitingRunnable(wait, "Waiting for a connection"));
		class PaneDialog implements Runnable, ComponentListener
		{
			JPanel	panel;
			Object	lock;
			
			public PaneDialog(JPanel panel, Object lock)
			{
				this.panel = panel;
				this.lock = lock;
			}
			
			@Override
			public void run()
			{
				final JDialog waitDialog = new JDialog(frame, "Waiting for Player");
				waitDialog.getContentPane().add(panel);
				waitDialog.pack();
				waitDialog.setSize(waitDialog.getWidth() + 15, waitDialog.getHeight());
				Point frameLocation = frame.getLocation();
				waitDialog.setLocation(frameLocation.x + (frame.getWidth() - waitDialog.getWidth())
				        / 2, frameLocation.y + (frame.getHeight() - waitDialog.getHeight()) / 2);
				waitDialog.addComponentListener(this);
				waitDialog.setModal(true);
				Thread t = new Thread(new Runnable()
				{
					@Override
					public void run()
					{
						waitDialog.setVisible(true);
					}
				});
				t.start();
				synchronized (lock)
				{
					try
					{
						lock.wait();
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				}
				t.interrupt();
				waitDialog.setVisible(false);
			}
			
			@Override
			public void componentHidden(ComponentEvent e)
			{
				synchronized (lock)
				{
					lock.notify();
				}
			}
			
			@Override
			public void componentMoved(ComponentEvent arg0)
			{}
			
			@Override
			public void componentResized(ComponentEvent arg0)
			{}
			
			@Override
			public void componentShown(ComponentEvent arg0)
			{}
		}
		Object lock = new Object();
		Thread showPane = new Thread(new PaneDialog(waitingCenter, lock));
		class ServerStarter implements Runnable
		{
			Object	lock;
			
			public ServerStarter(Object lock)
			{
				this.lock = lock;
			}
			
			@Override
			public void run()
			{
				server = new Server(port);
				// The constructor will return when we have a connection. If that's the case, then
				// we want to close the JOptionPane
				synchronized (lock)
				{
					lock.notify();
				}
			}
			
		}
		Thread startServer = new Thread(new ServerStarter(lock));
		waitingThread.start();
		showPane.start();
		// Server start will return when we get a connection, and if that's the case, then we want
		// to join the other threads
		startServer.start();
		try
		{
			showPane.join();
			// If the user closed the JOptionPane, this will interrupt the accept call for the
			// server. Otherwise, the showPane was interrupted by the server thread because it found
			// a connection.
			if (startServer.isAlive())
				startServer.interrupt();
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		waitingThread.interrupt();
	}
	
	/**
	 * <code>private Object[] getGameMode(JFrame frame)</code>
	 * <p>
	 * Gets the address, port, and/or game mode from the user via a JOptionPane.
	 * </p>
	 * 
	 * @param frame
	 *            - The frame to attach the JOptionPane to.
	 * @return A {int, String, int, boolean} where the first int is the game mode: 0 for joining a
	 *         game, 1 for hosting a game, and -1 for closing the JOptionPane. The String is the
	 *         address of the game the user wants to join. The last int is the port number to
	 *         connect to (returns: 11111 by default, or whatever the user entered). The boolean
	 *         tells us whether the String is an IPv4 address (false) or a domain name (true).
	 */
	private Object[] getGameMode(JFrame frame)
	{
		// Used to determine whether or not the server address and port are okay.
		class InputListener implements ActionListener
		{
			private int			mode	= -1;
			private boolean		isName;
			private JTextField	address;
			private JTextField	port;
			
			public InputListener(JTextField address)
			{
				this.address = address;
			}
			
			public void setPort(JTextField port)
			{
				this.port = port;
			}
			
			@Override
			// Checks the URL to make sure it's an IPv4 address or it's a proper domain name. Closes
			// if it is or the user wants to host a server.
			public void
			        actionPerformed(ActionEvent e)
			{
				Component c = (Component) e.getSource();
				if (e.getSource() instanceof JButton)
				{
					if (e.getActionCommand().equals("OK"))
					{
						if (!checkURL(address.getText()))
							return;
						if (!checkPort(port.getText()))
							return;
						mode = 0;
					}
					else if (e.getActionCommand().equals("Host Server"))
						mode = 1;
				}
				else if (e.getSource() instanceof JTextField)
				{
					if (!checkURL(address.getText()))
						return;
					if (!checkPort(port.getText()))
						return;
					mode = 0;
				}
				Container window = c.getParent();
				while (!(window instanceof Window))
					window = window.getParent();
				if (window instanceof Window)
				{
					Window windowWindow = (Window) window;
					windowWindow.setVisible(false);
					windowWindow.dispose();
				}
			}
			
			// Checks to make sure the port is a number (or empty (i.e. default))
			private boolean checkPort(String text)
			{
				// If the port is empty, then treat it as the default
				if (text.isEmpty())
					return true;
				// Otherwise, make sure it is a number
				try
				{
					Integer.parseInt(text);
				}
				catch (NumberFormatException e)
				{
					// It's not a number, so it's not a good port
					return false;
				}
				return true;
			}
			
			// There's a class in Apache Commons that does this, but I want to avoid bloating up our
			// program with external jars.
			private boolean checkURL(String text)
			{
				// We will consider a URL to be good in two cases: it's an IPv4 address, or it's a
				// domain address.
				// Domain name formatting. A domain name can have any sort of length periods
				// If a protocol is specified, we don't need to know that
				if (text.contains("://"))
				{
					address.setText(address.getText().substring(
					        address.getText().indexOf("://") + 3));
					text = address.getText();
				}
				// IPv4 check. IPv4 contains only periods and numbers, the port should be specified
				// separately. A domain name must end with a word containing no numbers.
				try
				{
					String[] numbers = text.split("\\.");
					// Any domain/address has at least 3 parts. We assume at least 2, and the 3rd to
					// be the implied www.
					if (numbers.length < 2)
						return false;
					// Try parsing the last number as a number. If it is, then we expect an IPv4
					// address, otherwise we treat it as a domain name
					try
					{
						// This should throw the exception that this block is trying to catch
						Integer.parseInt(numbers[numbers.length - 1]);
						// It is a number, so the previous *3* should also be numbers
						int count = 0;
						try
						{
							for (int i = 0; i < numbers.length - 1; i++)
							{
								Integer.parseInt(numbers[i]);
								count++;
							}
						}
						catch (NumberFormatException e)
						{
							// One of them failed to parse
							return false;
						}
						// So all that is contained in the given address is numbers (if it gets to
						// this point). We should have checked 3 numbers, otherwise, we have a badly
						// formatted address
						if (count == 3)
						{
							isName = false;
							return true;
						}
						return false;
					}
					catch (NumberFormatException e)
					{
						// If any of the separations is an empty string, then the user inputed
						// garbage
						for (String s : numbers)
							if (s.isEmpty())
								return false;
						// Since it failed to parse, we expect a word with no numbers
						for (Character c : numbers[numbers.length - 1].toCharArray())
							if (Character.isDigit(c))
								return false;
					}
				}
				catch (IndexOutOfBoundsException e)
				{
					// Whatever the user inputed, it's not right
					return false;
				}
				// If it never failed a check, then let's try connecting to whatever the user gave
				// us.
				isName = true;
				return true;
			}
			
			// Returns the gameMode as determined from the user input
			public int getMode()
			{
				return mode;
			}
			
			// Returns whether or not the specified address is a name or ip
			public boolean isName()
			{
				return isName;
			}
		}
		// Set up the info array
		Object[] modeInfo = new Object[4];
		// Format the top row of the JOptionPane so that we have a JLabel followed by a JTextBox
		JPanel addressPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
		addressPanel.add(new JLabel("Server name:"));
		JTextField address = new JTextField(20);
		// Set up the GUI listener
		InputListener listener = new InputListener(address);
		address.setName("address");
		address.addActionListener(listener);
		addressPanel.add(address);
		// Same for the second row, but we want the port number
		JPanel portPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
		portPanel.add(new JLabel("Port Number (leave blank for default, 11111):"));
		JTextField port = new JTextField(5);
		port.addActionListener(listener);
		listener.setPort(port);
		portPanel.add(port);
		// Set up the button panel to hold two buttons
		JButton confirm = new JButton("OK");
		confirm.addActionListener(listener);
		JButton host = new JButton("Host Server");
		host.addActionListener(listener);
		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
		buttonPanel.add(host);
		buttonPanel.add(confirm);
		// Set up the panel to hold the two and arrange them on top of each other
		JPanel optionPanel = new JPanel(new GridLayout(3, 1, 0, 0));
		optionPanel.add(addressPanel);
		optionPanel.add(portPanel);
		optionPanel.add(buttonPanel);
		Object[] options = new Object[] {optionPanel};
		Object initialValue = address;
		// Center the message of the JOptionPane.
		JPanel messagePanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
		messagePanel.add(new JLabel("Input the address of the host:"));
		JOptionPane.showOptionDialog(frame, messagePanel, "Host or Join Server",
		        JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, initialValue);
		modeInfo[0] = listener.getMode();
		modeInfo[1] = address.getText();
		if (port.getText().isEmpty())
			modeInfo[2] = 11111;
		else
			modeInfo[2] = Integer.parseInt(port.getText());
		modeInfo[3] = listener.isName();
		return modeInfo;
	}
	
	private void setupGame()
	{
		int boardSize = board.getBoard().length;
		// Player 1 is the player on this computer, Player 2 is the player on the
		// other side of the network
		players = new Player[2];
		players[0] = new Player(1);
		players[1] = new Player(2);
		// maxPieces = (10*4/10)*10=4*10=40
		int maxPieces = (boardSize * 4 / 10) * boardSize;
		// Setup the piece selector to select the pieces
		selector =
		        new PieceSelector(new Dimension(256, 512), maxPieces, 1, new Point(
		                frame.getWidth(), frame.getHeight() - 512), lock, this, board, canvas,
		                frame);
		selector.setPlayerPieceLocations(players[0].getPieceLocations());
		canvas.setSelector(selector);
		// Notify Sabina's messages thing that I want to start setting up pieces
		(new Thread(canvas)).start();
		canvas.addBoard(board);
		canvas.isSelecting.set(true);
		canvas.inGame.set(false);
		stream.inGame.set(false);
		stream.inSetup.set(true);
		// Notify Sabina's messages thing that I want to start setting up pieces
		
		// Thread for adding the opponent's pieces to the board
		class OpponentAdding implements Runnable
		{
			
			private LinkedBlockingQueue<HashMap<Point, String>>	queue;
			private HashMap<Point, String>			            pieceLocations;
			
			public OpponentAdding(LinkedBlockingQueue<HashMap<Point, String>> queue)
			{
				this.queue = queue;
				pieceLocations = new HashMap<Point, String>();
			}
			
			@Override
			public void run()
			{
				while (pieceLocations.size() < totalPieces)
				{
					try
					{
						pieceLocations.putAll(queue.take());
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				}
				notified.set(true);
			}
			
			public HashMap<Point, String> getPieceLocations()
			{
				return pieceLocations;
			}
			
		}
		
		// Start the thread for adding pieces to the pieceLocation map
		OpponentAdding opponent = new OpponentAdding(tempPieces);
		Thread opponentAddingThread = new Thread(opponent);
		opponentAddingThread.start();
		
		// Thread for getting pieces from the playa
		selector.showFrame();
		// Wait until the player is done selecting
		wait(lock);
		HashMap<Point, Piece> thisPlayersPieces = players[0].getPieceLocations();
		
		// Send the pieces to the opponent
		for (Entry<Point, Piece> piece : thisPlayersPieces.entrySet())
		{
			stream.sendMessage("1" + piece.getKey().x + "." + piece.getKey().y + "."
			        + piece.getValue().toString());
		}
		// If the other player is still selecting, tell the user that
		boolean paneShowed = false;
		Thread waitingThread = null;
		Thread modalThread = null;
		final JDialog waitingForOpponentPane = new JDialog(frame, "Waiting for Opponent");
		if (opponentAddingThread.isAlive())
		{
			JLabel waiting = new JLabel("Waiting for opponent to finish selection");
			JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
			panel.add(waiting);
			waitingForOpponentPane.getContentPane().add(panel);
			waitingForOpponentPane.pack();
			waitingForOpponentPane.setSize(waitingForOpponentPane.getWidth() + 15,
			        waitingForOpponentPane.getHeight());
			Point frameLoc = frame.getLocation();
			waitingForOpponentPane.setLocation(frameLoc.x
			        + (frame.getWidth() - waitingForOpponentPane.getWidth()) / 2, frameLoc.y
			        + (frame.getHeight() - waitingForOpponentPane.getHeight()) / 2);
			waitingThread =
			        new Thread(new WaitingRunnable(waiting,
			                "Waiting for opponent to finish selection"));
			waitingThread.start();
			waitingForOpponentPane.setModal(true);
			modalThread = new Thread(new Runnable()
			{
				@Override
				public void run()
				{
					waitingForOpponentPane.setVisible(true);
				}
			});
			modalThread.start();
			paneShowed = true;
		}
		try
		{
			opponentAddingThread.join();
			if (paneShowed)
			{
				waitingThread.interrupt();
				modalThread.interrupt();
				waitingForOpponentPane.setVisible(false);
				waitingForOpponentPane.dispose();
			}
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		stream.inSetup.set(false);
		stream.inGame.set(false);
		
		// Take the pieces that are in the opponent's getting stuff method and put it into the game
		Piece[][] tempBoard = board.getBoard();
		for (Entry<Point, String> piece : opponent.getPieceLocations().entrySet())
		{
			try
			{
				// Tell the opponent that it is his/her piece
				Piece p =
				        PieceUtilities.PIECES.get(piece.getValue())
				                .getConstructor((new Color[] {}).getClass())
				                .newInstance((Object) PieceUtilities.blueGradient);
				p.backShowing.set(true);
				players[1].addPiece(p, new Point(piece.getKey().x, piece.getKey().y));
				
				// Add the piece to the board
				tempBoard[piece.getKey().x][piece.getKey().y] = p;
			}
			catch (InstantiationException e)
			{
				e.printStackTrace();
			}
			catch (IllegalAccessException e)
			{
				e.printStackTrace();
			}
			catch (IllegalArgumentException e)
			{
				e.printStackTrace();
			}
			catch (InvocationTargetException e)
			{
				e.printStackTrace();
			}
			catch (NoSuchMethodException e)
			{
				e.printStackTrace();
			}
			catch (SecurityException e)
			{
				e.printStackTrace();
			}
		}
		
		return;
	}
	
	private void playGame()
	{
		if (server == null)
		{
			if (!notified.get())
				wait(lock);
		}
		else
		{
			int temp = 0;// new Random().nextInt(2);
			if (temp == 1)
			{
				meFirst.set(true);
				stream.sendMessage("10");
			}
			else if (temp == 0)
			{
				meFirst.set(false);
				stream.sendMessage("11");
			}
		}
		if (meFirst.get())
		{
			currentPlayer = players[0];
			System.out.println("You go first!");
		}
		else
		{
			currentPlayer = players[1];
			System.out.println("You go second.");
		}
		stream.inGame.set(true);
		stream.inSetup.set(false);
		
		// Make a waiting move thing. Only used when waiting for the other player to make a move
		waitingMove = new JDialog(frame, "Waiting for move...");
		String waitingString = "Waiting for the opponent to make a move";
		JLabel waiting = new JLabel(waitingString);
		WaitingRunnable runnable = new WaitingRunnable(waiting, waitingString);
		Thread waitingThread = new Thread(runnable);
		JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
		panel.add(waiting);
		waitingMove.getContentPane().add(panel);
		updatePosition(waitingMove);
		
		// Initialize the pointQueue and pieceQueue
		pointQueue = new Point[2];
		pieceQueue = new Piece[2];
		
		canvas.isSelecting.set(false);
		canvas.inGame.set(true);
		while (winner == null)
		{
			// Players[0] is the local player. If that is the current player,
			// get the local player's move
			if (currentPlayer.equals(players[0]))
			{
				int good = 1;
				do
				{
					// This wait is broken by a fired BoardClickedEvent. As the next dialog shows,
					// the
					// game waits for the player to make a move and then asks the player to confirm.
					// If
					// they say "yay," then we continue, otherwise we clear the current selection
					// and
					// wait again.
					wait(moveLock);
					good =
					        JOptionPane.showConfirmDialog(frame, "Execute move and end turn?",
					                "Make Move", JOptionPane.YES_NO_OPTION);
					if (good != 0)
					{
						for (int i = 0; i < pieceQueue.length; i++)
						{
							pieceQueue[i] = null;
							pointQueue[i] = null;
						}
						canvas.clearHighlightLocation();
					}
				}
				while (good != 0);
				// Clear the highlightedPieces
				canvas.clearHighlightLocation();
				String movement =
				        "1" + pointQueue[0].x + "." + pointQueue[0].y + "." + pointQueue[1].x + "."
				                + pointQueue[1].y;
				stream.sendMessage(movement);
				// Execute whatever action is necessary.
				executeAction(players[0], players[1], pieceQueue, pointQueue);
				// Clear the selection after we're done
				for (int i = 0; i < pieceQueue.length; i++)
				{
					pieceQueue[i] = null;
					pointQueue[i] = null;
				}
				// Check to see if the last move was a finishing movie
				if (winner != null)
				{
					canvas.flipPieces(players[1].getPieceLocations(), lock);
					wait(lock);
					break;
				}
			}
			// player[1] is the opponent on the other side of the internet
			else
			{
				// Start the waiting animation
				if (!waitingThread.isAlive() && !waitingThread.isInterrupted())
					waitingThread.start();
				else if (waitingThread.isInterrupted())
				{
					waitingThread = new Thread(runnable);
					waitingThread.start();
				}
				updatePosition(waitingMove);
				waitingMove.setModal(true);
				Thread modalThread = new Thread(new Runnable()
				{
					@Override
					public void run()
					{
						waitingMove.setVisible(true);
					}
				});
				modalThread.start();
				wait(lock);
				modalThread.interrupt();
				waitingMove.setVisible(false);
				// Execute the move
				executeAction(players[1], players[0], pieceQueue, pointQueue);
				if (winner != null)
				{
					canvas.flipPieces(players[1].getPieceLocations(), lock);
					wait(lock);
					break;
				}
			}
			
			// Switch the players
			if (currentPlayer.equals(players[0]))
			{
				currentPlayer = players[1];
			}
			else
			{
				currentPlayer = players[0];
			}
		}
		
		// Winner is teh you!
		if (winner.equals(players[0]))
		{
			JOptionPane.showMessageDialog(frame, "Congratulations, you won!", "Victory!",
			        JOptionPane.INFORMATION_MESSAGE);
		}
		else if (winner.equals(players[1]))
		{
			JOptionPane.showMessageDialog(frame, "Aww, you lost.", "Defeat.",
			        JOptionPane.INFORMATION_MESSAGE);
		}
	}
	
	private void updatePosition(JDialog waitingMove)
	{
		waitingMove.pack();
		waitingMove.setSize(waitingMove.getWidth() + 15, waitingMove.getHeight());
		Point frameLoc = frame.getLocation();
		waitingMove.setLocation(frameLoc.x + (frame.getWidth() - waitingMove.getWidth()) / 2,
		        frameLoc.y + (frame.getHeight() - waitingMove.getHeight()) / 2);
	}
	
	/**
	 * Allows another thread or object to place the opponent's piece
	 * 
	 * @param pieceToPlace
	 *            Placement coordinates. pieceToPlace[0] = x, pieceToPlace[1] = y, pieceToPlace[2] =
	 *            name of piece
	 */
	public void setOpponentPiece(String[] pieceToPlace)
	{
		HashMap<Point, String> tempie = new HashMap<Point, String>();
		tempie.put(new Point(Integer.parseInt(pieceToPlace[0]), Integer.parseInt(pieceToPlace[1])),
		        pieceToPlace[2]);
		try
		{
			tempPieces.put(tempie);
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * Allows another thread to set the move that the opponent made
	 * 
	 * @param move
	 *            Movement coordinates. move[0] = initial x, move[1] = initial y, move[2] = final x,
	 *            move[3] = final y
	 */
	public void setOpponentMove(String[] move)
	{
		pointQueue[0] = new Point(Integer.parseInt(move[0]), Integer.parseInt(move[1]));
		pointQueue[1] = new Point(Integer.parseInt(move[2]), Integer.parseInt(move[3]));
		
		// Gets the pieces for the corresponding points
		pieceQueue[0] = board.getBoard()[pointQueue[0].x][pointQueue[0].y];
		pieceQueue[1] = board.getBoard()[pointQueue[1].x][pointQueue[1].y];
		
		// Notify the main loop that the opponent has made a move
		synchronized (lock)
		{
			lock.notify();
		}
	}
	
	/**
	 * 
	 */
	public void setFirstPlayer(String first)
	{
		if (first.equals("1"))
		{
			meFirst.set(true);
		}
		else
		{
			meFirst.set(false);
		}
		synchronized (lock)
		{
			lock.notify();
		}
	}
	
	/**
	 * <code>private void executeAction(Player actor, Player reciever, Piece[] pieceQueue, Point[] pointQueue)</code>
	 * <p>
	 * Executes a set of actions based on the pieces and their locations. It will either move a
	 * piece to an empty location, destroy a piece and move the surviving piece to its old location,
	 * destroy two pieces, or declare a winner.
	 * </p>
	 * 
	 * @param actor
	 *            - The player who case the first stone.
	 * @param reciever
	 *            - The player who must turn the other cheek.
	 * @param pieceQueue
	 *            - The pieces involved in the conflict.
	 * @param pointQueue
	 *            - The locations of the pieces involved in the conflict.
	 */
	private void
	        executeAction(Player actor, Player reciever, Piece[] pieceQueue, Point[] pointQueue)
	{
		// Assign the pieces their corresponding role. The attacking piece belongs to the acting
		// Player.
		Piece att = pieceQueue[0];
		Piece def = pieceQueue[1];
		Point attLoc = pointQueue[0];
		Point defLoc = pointQueue[1];
		// For empty spaces, simply move the other piece.
		if (def.isEmpty())
		{
			// Tell the player that their piece is now in this location
			actor.updatePiece(att, attLoc, defLoc);
			// Animate the move
			canvas.movePiece(att, def, attLoc, defLoc, lock);
			wait(lock);
			// No need to make new objects, just swap the references
			board.getBoard()[defLoc.x][defLoc.y] = att;
			board.getBoard()[attLoc.x][attLoc.y] = def;
			return;
		}
		// Flip over the defending piece
		HashMap<Point, Piece> flip = new HashMap<Point, Piece>();
		flip.put(defLoc, def);
		canvas.flipPieces(flip, lock);
		wait(lock);
		// For non empty spaces (i.e. two pieces, use the piece comparator to get the winning piece)
		Piece surv = PieceComparator.comparePiece(att, def);
		Piece loser = null;
		Point losLoc = null;
		Point winLoc = null;
		Player win = null;
		Player los = null;
		// The comment explaining this would be too long, rather I hope the variable names are clear
		// enough. As far as why this works, one must understand the nature of Java. Using Object ==
		// Object rather than Object.equals(Object) compares the references, not the contents. In
		// completely correct terms: it compares the memory location of the two objects rather than
		// executing a set of algorithms defined by the .equals() method.
		if (surv == att)
		{
			win = actor;
			los = reciever;
			loser = def;
			winLoc = attLoc;
			losLoc = defLoc;
		}
		else
		{
			win = reciever;
			los = actor;
			loser = att;
			winLoc = defLoc;
			losLoc = attLoc;
		}
		// The piece to destroy
		HashMap<Point, Piece> pieces = new HashMap<Point, Piece>();
		// If the losing piece is the flag, then we have a victor
		if (def.isFlag())
		{
			winner = win;
			pieces.put(defLoc, def);
			canvas.flipPieces(pieces, lock);
			wait(lock);
			return;
		}
		// If surv is null, then that means both pieces are destroyed
		if (surv == null)
		{
			// Indicate to both players to remove the piece at the specified location
			actor.decrementPiece(att, attLoc);
			reciever.decrementPiece(def, defLoc);
			// Animate the destruction of these pieces
			pieces.put(attLoc, att);
			pieces.put(defLoc, def);
			canvas.destroyPieces(pieces, lock);
			wait(lock);
			// We have to make two new empty pieces
			board.getBoard()[attLoc.x][attLoc.y] = new EmptyPiece();
			board.getBoard()[defLoc.x][defLoc.y] = new EmptyPiece();
			// Don't execute any of the further logic, as it's already been taken care of here
			return;
		}
		// The losing piece is to be destroyed
		pieces.put(losLoc, loser);
		// If the winner is a bomb, then we have to destroy both pieces
		if (surv.isBomb())
			pieces.put(winLoc, surv);
		canvas.destroyPieces(pieces, lock);
		wait(lock);
		// Update the player's boards, and the main board
		los.decrementPiece(loser, losLoc);
		Piece empty = new EmptyPiece();
		board.getBoard()[losLoc.x][losLoc.y] = empty;
		// If the winner was the attacker, then move the attacker's piece to the now vacant loser's
		// square
		if (win == actor)
		{
			win.updatePiece(surv, winLoc, losLoc);
			canvas.movePiece(surv, empty, winLoc, losLoc, lock);
			wait(lock);
			board.getBoard()[losLoc.x][losLoc.y] = surv;
			board.getBoard()[winLoc.x][winLoc.y] = empty;
		}
		// Otherwise, flip back over the defender's piece
		else
		{
			// If the defender's piece was a bomb, it too should be destroyed
			if (surv.isBomb())
			{
				win.decrementPiece(surv, winLoc);
				board.getBoard()[winLoc.x][winLoc.y] = new EmptyPiece();
			}
			// If not, just flip over the normal piece.
			else
			{
				canvas.flipPieces(flip, lock);
				wait(lock);
			}
		}
	}
	
	/**
	 * <code>private void wait(Object lock)</code>
	 * <p>
	 * Causes the calling thread to wait indefinitely for lock to be notified.
	 * </p>
	 * 
	 * @param lock
	 *            - The Object used to lock the caller's thread.
	 */
	private void wait(Object lock)
	{
		synchronized (lock)
		{
			try
			{
				lock.wait();
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
	
	@Override
	/**Allows the game to respond to MouseEvents fired on GameCanvas*/
	public void boardClicked(BoardEvent e)
	{
		// If we are currently in selection mode, then place or remove a piece depending on the
		// button
		if (canvas.isSelecting.get() && selector != null)
		{
			// If the click isn't in the approved region, then don't do anything
			if (!checkRegion(selector.getPlayer(), e.getY()))
				return;
			// Get the piece at the current location
			Piece current = board.getBoard()[e.getX()][e.getY()];
			// If it's empty, then add the desired piece to the location, and inform all relevant
			// objects of this
			if (current.isEmpty() && e.getButton() == InputEvent.BUTTON1_MASK)
			{
				board.getBoard()[e.getX()][e.getY()] = e.getPiece();
				players[selector.getPlayer() - 1].addPiece(e.getPiece(),
				        new Point(e.getX(), e.getY()));
				selector.decrementPiece(e.getPiece());
			}
			// If the player right clicked, then they want to remove the piece at that location (but
			// we can only do that for non-empty blocks and not BlockPieces.
			else if (!current.isEmpty() && !current.isBlock()
			        && e.getButton() == InputEvent.BUTTON3_MASK)
			{
				// Inform the relevant objects that the piece at the current location is to be
				// removed
				Piece oldPiece = board.getBoard()[e.getX()][e.getY()];
				board.getBoard()[e.getX()][e.getY()] = new EmptyPiece();
				players[selector.getPlayer() - 1].decrementPiece(oldPiece,
				        new Point(e.getX(), e.getY()));
				selector.incrementPiece(oldPiece);
			}
		}
		// Logic for when a board is clicked and we are in game.
		if (canvas.inGame.get() && e.getButton() == InputEvent.BUTTON1_MASK)
		{
			if (!currentPlayer.equals(players[0]))
				return;
			Piece p = e.getPiece();
			Point loc = new Point(e.getX(), e.getY());
			// Getting the piece of the player
			if (players[0].getPieceLocations().containsKey(loc))
			{
				// Making sure not to add unmovable pieces
				if (!p.isBomb() && !p.isFlag())
				{
					canvas.clearHighlightLocation();
					pieceQueue[0] = p;
					pointQueue[0] = loc;
					canvas.highlightLocation(loc, new Color(0, 200, 0, 128));
				}
			}
			// It would be weird for this code to initiate an attack if the player selected an
			// enemy piece first
			else if (pieceQueue[0] == null)
				return;
			// Getting the target square for that piece
			else
			{ // Check to make sure the move is legal
				if (p.isBlock())
					return;
				// Check to make sure the other click location is in a line, is 1 grid away if
				// it is a regular piece, and doesn't traverse any illegal squares if it is a
				// scout
				if (!checkDistance(pieceQueue[0], p, pointQueue[0], loc))
					return;
				pieceQueue[1] = p;
				pointQueue[1] = loc;
				stream.sendMessage("1" + pointQueue[0].x + "." + pointQueue[0].y + "."
				        + pointQueue[1].x + "." + pointQueue[1].y);
				canvas.highlightLocation(loc, new Color(200, 0, 0, 128));
				// We should notify the game thread
				synchronized (moveLock)
				{
					moveLock.notify();
				}
			}
		}
	}
	
	/**
	 * <code>private boolean checkDistance(Piece pSrc, Piece pDest, Point src, Point dest)</code>
	 * <p>
	 * Checks the line between two pieces and makes sure a move is allowable. A move is allowable if
	 * the destination square is not a diagonal location or BlockPiece, and it is one square away
	 * from the source piece (or there is nothing between it and the other piece if it is a scout).
	 * </p>
	 * 
	 * @param pSrc
	 *            - The piece that desires to move.
	 * @param pDest
	 *            - The piece at the location the moving piece wants to go to.
	 * @param src
	 *            - The location of the piece that desires to move.
	 * @param dest
	 *            - The location that the piece wants to move to.
	 * @return True if the move is allowable. False otherwise.
	 */
	private boolean checkDistance(Piece pSrc, Piece pDest, Point src, Point dest)
	{
		double distance =
		        Math.sqrt(Math.pow(src.getX() - dest.getX(), 2.0)
		                + Math.pow(src.getY() - dest.getY(), 2.0));
		// Check to see if it has a fractional part. If it does, then the other location is a
		// diagonal location
		if (Math.floor(distance) - distance != 0.0)
			return false;
		// If the distance is greater than 1, and it's not a scout, then that's an illegal move
		if (distance > 1.0 && !pSrc.isScout())
			return false;
		// If it is a scout, get the direction of the move and make sure there's no other piece in
		// the way
		else
		{
			// The src and dest are horizontal
			if (src.x - dest.x != 0)
			{
				int startX = 0, endX = 0;
				if (src.x > dest.x)
				{
					startX = src.x;
					endX = dest.x;
				}
				else
				{
					endX = src.x;
					startX = dest.x;
				}
				// Don't check the starting grid (it could be a piece or empty, doesn't matter to
				// us)
				startX--;
				// Don't check the same grid as the original piece either
				while (startX > endX)
				{
					// The src and dest y are the same
					if (!board.getBoard()[startX][src.y].isEmpty())
						return false;
					startX--;
				}
			}
			// The src and dest are vertical
			else
			{
				int startY = 0, endY = 0;
				if (src.y > dest.y)
				{
					startY = src.y;
					endY = dest.y;
				}
				else
				{
					endY = src.y;
					startY = dest.y;
				}
				// Don't check the starting grid (it could be a piece or empty, doesn't matter to
				// us)
				startY--;
				// Don't check the same grid as the original piece either
				while (startY > endY)
				{
					// The src and dest x are the same
					if (!board.getBoard()[src.x][startY].isEmpty())
						return false;
					startY--;
				}
			}
		}
		return true;
	}
	
	/**
	 * <code>private boolean checkRegion(int player, int x, int y)</code>
	 * <p>
	 * Checks to make sure the player's piece is in the approved region. It is understood that the
	 * entire width of the board is acceptable.
	 * </p>
	 * 
	 * @param player
	 *            - An int representing which player is placing their piece.
	 * @param y
	 *            - An int to represent the y-grid location that the piece wants to be placed at
	 * @return True if the piece belongs in this region. False otherwise.
	 */
	private boolean checkRegion(int player, int y)
	{
		if (player == 1)
		{
			if (y < board.getBoard().length / 2 + 1)
				return false;
		}
		else
		{
			if (y > board.getBoard().length / 2 - 1)
				return false;
		}
		return true;
	}
	
	class WaitingRunnable implements Runnable
	{
		JLabel	waiting;
		String	message;
		
		public WaitingRunnable(JLabel waiting, String message)
		{
			this.waiting = waiting;
			this.message = message;
		}
		
		@Override
		public void run()
		{
			try
			{
				long counter = 0L;
				int maxDots = 4;
				while (true)
				{
					String dots = "";
					for (int i = 0; i < counter % maxDots; i++)
						dots += ".";
					counter++;
					waiting.setText(message + dots);
					Thread.sleep(500);
				}
			}
			catch (InterruptedException e)
			{// Do nothing, it is handled by the PaneDialog thread.
			}
		}
	}
}
